﻿using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using UtilZ.Dotnet.Ex.Json;

namespace UtilZ.Dotnet.Ex.Communication.Net.Web.JWT
{
    /// <summary>
    /// jwt辅助类
    /// </summary>
    public class JWTHelper
    {
        /*******************************************************************
         * eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsIm5iZiI6IjEzMjQ2NSIsInVpZCI6IjExMSJ9.k0jgp4dab6xRlwqsP6ePUJdeny85NY78VriKfSQB7e8=
         * JWT由三段组成,用点分隔
         * 头部.载荷.摘要签名
         * 第一段,又称为头部,包含类型和算法,类型固定为JWT,算法有几种可选 
         * eg:
         * {
         *      "type":"JWT",
         *      "alg":"HS256"
         * }
         * 
         * 第二段为载荷,载荷为有效信息,通常由三类内容(标准声明、公共声明、私有声明)组成,
         * 内存当中为一个key和value都是string的字典存放
         * 
         * 标准声明
         * 标准中注册的声明有以下(建议,但不强制使用)
         * iss          jwt签发者
         * sub          jwt所面向的用户
         * aud          jwt接收者
         * exp          wt过期时间,这个过期时间必须要大于签发时间
         * nbf          定义在什么时间之前,该jwt都是不可用的
         * iat          jwt签发时间
         * jti          jwt唯一身份标识,主要用来作为一次token,从而回避重放攻击
         *
         * 公共声明:
         * 可添加任何信息,一般添加和业务相关的必要信息
         * 
         * 私有声明:
         * 私有声明是提供者和消费者所共同定义的声明,想放什么信息都可以,只要对方认可就行
         * 
         * 第三部分为签摘要,用于防截获篡改
         *******************************************************************/


        #region 载荷-标准申明项
        /// <summary>
        /// jwt签发者
        /// </summary>
        public const string ISS = "iss";

        /// <summary>
        /// jwt所面向的用户
        /// </summary>
        public const string SUB = "sub";

        /// <summary>
        /// jwt接收者
        /// </summary>
        public const string AUD = "aud";

        /// <summary>
        /// jwt过期时间,这个过期时间必须要大于签发时间
        /// </summary>
        public const string EXP = "exp";

        /// <summary>
        /// 定义在什么时间之前,该jwt都是不可用的
        /// </summary>
        public const string NBF = "nbf";

        /// <summary>
        /// jwt签发时间
        /// </summary>
        public const string IAT = "iat";

        /// <summary>
        /// jwt唯一身份标识,主要用来作为一次token,从而回避重放攻击
        /// </summary>
        public const string JTI = "jti";
        #endregion



        #region 头部
        /// <summary>
        /// 类型
        /// </summary>
        public const string TPY = "typ";

        /// <summary>
        /// jwt算法
        /// </summary>
        public const string ALG = "alg";

        private const string TYPE = "JWT";
        private const string ALGORITHM = "HS256";
        #endregion


        /// <summary>
        /// 生成jwt字符串
        /// </summary>
        /// <param name="papayloadDic">载荷字典集合</param>
        /// <param name="signKey">摘要签名字符串</param>
        /// <param name="encoding">编码方式,为null默认使用UTF8</param>
        /// <returns>jwt字符串</returns>
        public static string GenerateJwt(Dictionary<string, string> papayloadDic, string signKey, Encoding encoding = null)
        {
            if (papayloadDic == null)
            {
                throw new ArgumentNullException(nameof(papayloadDic));
            }

            if (string.IsNullOrWhiteSpace(signKey))
            {
                throw new ArgumentNullException(nameof(signKey));
            }

            encoding = GetEncoding(encoding);

            var jwtHeadDic = new Dictionary<string, string>();
            jwtHeadDic.Add(TPY, TYPE);
            jwtHeadDic.Add(ALG, ALGORITHM);
            string headJson = JsonSerializer.SerializeObject(jwtHeadDic);
            string papayloadJson = JsonSerializer.SerializeObject(papayloadDic);

            string jwtHead = JwtBase64(headJson, encoding);
            string jwtPapayload = JwtBase64(papayloadJson, encoding);
            string jwtSign = JwtToHMACSHA256(signKey, $"{jwtHead}.{jwtPapayload}", encoding);
            string jwt = $"{jwtHead}.{jwtPapayload}.{jwtSign}";
            return jwt;
        }


        /// <summary>
        /// 验证jwt
        /// </summary>
        /// <param name="jwt">要验证的jwt</param>
        /// <param name="signKey">摘要签名字符串</param>
        /// <param name="encoding">编码方式,为null默认使用UTF8</param>
        /// <returns>验证结果e</returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static JwtResult ValidateJwt(string jwt, string signKey, Encoding encoding = null)
        {
            if (string.IsNullOrWhiteSpace(jwt))
            {
                throw new ArgumentNullException(nameof(jwt));
            }

            if (string.IsNullOrWhiteSpace(signKey))
            {
                throw new ArgumentNullException(nameof(signKey));
            }

            string[] strs = jwt.Split('.');
            if (strs.Length != 3)
            {
                return JwtResult.Invalid;
            }

            encoding = GetEncoding(encoding);
            string headJson = JwtBase64Decode(strs[0], encoding);
            var jwtHeadDic = JsonSerializer.DeserializeObject<Dictionary<string, string>>(headJson);
            string str;
            if (jwtHeadDic.TryGetValue(TPY, out str))
            {
                if (!string.Equals(str, TYPE))
                {
                    return JwtResult.Invalid;
                }
            }
            else
            {
                return JwtResult.Invalid;
            }

            if (jwtHeadDic.TryGetValue(ALG, out str))
            {
                if (!string.Equals(str, ALGORITHM))
                {
                    return JwtResult.Invalid;
                }
            }
            else
            {
                return JwtResult.Invalid;
            }

            string jwtSign = JwtToHMACSHA256(signKey, $"{strs[0]}.{strs[1]}", encoding);
            bool valid = string.Equals(strs[2], jwtSign, StringComparison.OrdinalIgnoreCase);
            if (valid)
            {
                string papayloadJson = JwtBase64Decode(strs[1], encoding);
                var papayload = JsonSerializer.DeserializeObject<Dictionary<string, string>>(papayloadJson);
                return new JwtResult(valid, papayload);
            }
            else
            {
                return JwtResult.Invalid;
            }
        }



        private static string JwtToHMACSHA256(string secretKey, string text, Encoding encoding)
        {
            using (var hs256 = new HMACSHA256())
            {
                hs256.Key = encoding.GetBytes(secretKey);
                byte[] hash = hs256.ComputeHash(encoding.GetBytes(text));
                return Convert.ToBase64String(hash);
            }
        }

        private static string JwtBase64(string text, Encoding encoding)
        {
            byte[] buffer = encoding.GetBytes(text);
            string jwtStr = Convert.ToBase64String(buffer);
            return jwtStr.Replace('+', '-').Replace('/', '_').TrimEnd('=');
        }

        private static string JwtBase64Decode(string base64Text, Encoding encoding)
        {
            string jwtStr = base64Text.Replace('-', '+').Replace('_', '/');
            int mod = jwtStr.Length % 4;
            if (mod == 2)
            {
                jwtStr += "==";
            }
            else if (mod == 3)
            {
                jwtStr += "=";
            }

            byte[] buffer = Convert.FromBase64String(jwtStr);
            return encoding.GetString(buffer);
        }


        /// <summary>
        /// 验证jwt
        /// </summary>
        /// <param name="encoding">编码方式,为null默认使用ASCII</param>
        /// <returns>Encoding</returns>
        private static Encoding GetEncoding(Encoding encoding)
        {
            if (encoding == null)
            {
                encoding = Encoding.UTF8;
            }

            return encoding;
        }
    }


    /// <summary>
    /// jwt结果
    /// </summary>
    public class JwtResult
    {
        private static JwtResult _invalid = null;
        /// <summary>
        /// 无效结果
        /// </summary>
        public static JwtResult Invalid
        {
            get
            {
                if (_invalid == null)
                {
                    _invalid = new JwtResult(false, null);
                }

                return _invalid;
            }
        }

        /// <summary>
        /// 数据是否有效[true:有效;false:无效]
        /// </summary>
        public bool Succes { get; private set; }

        /// <summary>
        /// 载荷数据字典集合
        /// </summary>
        public Dictionary<string, string> Papayload { get; private set; }


        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="succes">数据是否有效[true:有效;false:无效]</param>
        /// <param name="papayload">载荷数据字典集合</param>
        public JwtResult(bool succes, Dictionary<string, string> papayload)
        {
            this.Succes = succes;
            this.Papayload = papayload;
        }
    }
}
